﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace THenu.TinyHourseLib.EventAndDelegate
{
    public class WeakEventHandler<T>
        where T : EventArgs
    {
        private delegate void EventHandlerThunk(object @this, object sender, T e);

        private static int g_NextThunkID = 1;

        private WeakReference m_TargetRef;
        private EventHandlerThunk m_Thunk;
        private EventHandler<T> m_Handler;
        public WeakEventHandler(EventHandler<T> eventHandler)
        {
            m_TargetRef = new WeakReference(eventHandler.Target);
            m_Thunk = CreateDynamicThunk(eventHandler); ;
            m_Handler = Invok;
        }

        public void Invok(object sender, T e)
        {
            object target = m_TargetRef.Target;
            if(target!=null)
                m_Thunk(target, sender, e);
        }
        public static implicit operator EventHandler<T>(WeakEventHandler<T> weh)
        {
            return weh.m_Handler; 
        }

        private EventHandlerThunk CreateDynamicThunk(EventHandler<T> eventHandler)
        {
            MethodInfo method = eventHandler.Method;
            Type declaringType = method.DeclaringType;

            int id = Interlocked.Increment(ref g_NextThunkID);

            DynamicMethod dm = new DynamicMethod("EventHandlerThunk" + id, typeof(void),
              new Type[] { typeof(object), typeof(object), typeof(T) }, declaringType);

            ILGenerator il = dm.GetILGenerator();

            // load and cast "this" pointer...
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, declaringType);

            // load arguments...
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);

            // call method...
            il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);

            // done...
            il.Emit(OpCodes.Ret);

            return (EventHandlerThunk)dm.CreateDelegate(typeof(EventHandlerThunk));
        }

    }
}
